package runner

import (
	"bytes"
	"ckeadm/constants"
	"ckeadm/util"
	"ckeadm/util/service"
	"fmt"
	"github.com/sirupsen/logrus"
	"io/ioutil"
	"os"
	"os/exec"
	"text/template"
)

var DockerServiceTemplate = template.Must(template.New("join").Parse(`
[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.com
[Service]
Type=notify
Environment=GOTRACEBACK=crash
ExecReload=/bin/kill -s HUP $MAINPID
Delegate=yes
KillMode=process
ExecStart={{.DockerBinDir}}/dockerd -H fd://
TasksMax=infinity
LimitNOFILE=100000
LimitNPROC=500000
LimitCORE=infinity
TimeoutStartSec=15min
TimeoutStopSec=15min
# restart the docker process if it exits prematurely
Restart=on-failure
StartLimitBurst=3
StartLimitInterval=60s

[Install]
WantedBy=multi-user.target
`[1:]))

type DockerService struct {
	ServiceName      string
	DockerBinDir     string
	CgroupDriver     string
	InsecureRegistry string
}

func NewDockerService(ServiceName, DockerBinDir, CgroupDriver, InsecureRegistry string) svc.Service {
	return &DockerService{
		ServiceName:      ServiceName,
		DockerBinDir:     DockerBinDir,
		CgroupDriver:     CgroupDriver,
		InsecureRegistry: InsecureRegistry,
	}
}

func (docker *DockerService) CheckServiceConfig() bool {
	// format = RegistrySecure
	var format = fmt.Sprintf(`{{$registry := index .RegistryConfig.IndexConfigs "%s"}}{{if $registry}}{{$registry.Secure}}{{end}}`, docker.InsecureRegistry)
	// #nosec
	if out, err := exec.Command("docker", "info", "-f", format).CombinedOutput(); err == nil {
		return string(out) == fmt.Sprintf("%t\n", false)
	}

	return false
}

func (docker *DockerService) CreateAndStartService(templates *template.Template, execPath string) error {
	logrus.Infof("trying to create service %s, cgroup driver: %s\n", docker.ServiceName, docker.CgroupDriver)

	var buff = bytes.NewBuffer([]byte{})
	_ = templates.Execute(buff, docker)

	service, err := ioutil.ReadAll(buff)
	if err != nil {
		return fmt.Errorf("error resolve template: %s", err.Error())
	}
	if err = util.WriteFile(fmt.Sprintf("/etc/systemd/system/%s.service", docker.ServiceName), string(service)); err != nil {
		return fmt.Errorf("error write service: %s", err.Error())
	}

	if err = util.WriteFile("/etc/docker/daemon.json", fmt.Sprintf(`{"insecure-registries": ["%s"],"exec-opts":["native.cgroupdriver=%s"],"live-restore":true}`, docker.InsecureRegistry, docker.CgroupDriver)); err != nil {
		return fmt.Errorf("error write daemon.json: %s", err.Error())
	}

	logrus.Infof("trying to enabled and start service %s ...\n", docker.ServiceName)
	if err = docker.RestartService(); err != nil {
		logrus.Warningf("service %s restart failed, reasion: %s\n", docker.ServiceName, err.Error())
	}

	logrus.Infof("wait for service become healthy: %s\n", docker.ServiceName)
	return docker.WaitForServiceRunning()
}

func (docker *DockerService) RestartService() error {
	// #nosec
	exec.Command("systemctl", "daemon-reload").Run()
	// #nosec
	exec.Command("systemctl", "enable", docker.ServiceName).Run()
	// #nosec
	return exec.Command("systemctl", "restart", docker.ServiceName).Run()
}

func (docker *DockerService) IsRunning() bool {
	// #nosec
	return exec.Command("docker", "ps").Run() == nil
}

func (docker *DockerService) WaitForServiceRunning() error {
	// wait for service start util restart timeout
	return util.PollImmediate(constants.WaitCallRetryInterval, constants.WaitRestartTimeout, func() (bool, error) {
		return docker.IsRunning(), nil
	})
}

func (docker *DockerService) RemoveService() error {
	// #nosec
	exec.Command("systemctl", "stop", docker.ServiceName).Run()
	// #nosec
	exec.Command("systemctl", "disable", docker.ServiceName).Run()
	return os.Remove(fmt.Sprintf("/etc/systemd/system/%s.service", docker.ServiceName))
}
